package structplugin

import (
	"encoding"
	"fmt"
	"reflect"
	"strconv"
)

func Unmarshal(values map[string]string, obj any) error {
	ot := reflect.TypeOf(obj)
	if ot.Kind() != reflect.Ptr {
		return fmt.Errorf("obj type must be &%s", ot.Name())
	}

	ov := reflect.ValueOf(obj).Elem()
	ot = ov.Type()

	for i := 0; i < ot.NumField(); i++ {
		fd := ot.Field(i)
		fv := ov.Field(i)
		val, ok := values[fd.Name]

		if !ok || val == "" || val == "0" {
			fv.Set(reflect.Zero(fd.Type))
			continue
		}

		err := parse(fd.Type, fv, val)
		if err != nil {
			return err
		}
	}
	return nil
}

func parse(fd reflect.Type, fv reflect.Value, val string) error {
	kind := fd.Kind()
	switch kind {
	case reflect.Ptr:
		fv.Set(reflect.New(fd.Elem()))
		return parse(fv.Elem().Type(), fv.Elem(), val)
	case reflect.Int64, reflect.Int32, reflect.Int, reflect.Int16, reflect.Int8:
		vint64, err := strconv.ParseInt(val, 10, 64)
		if err != nil {
			return err
		}
		fv.SetInt(vint64)
		return nil
	case reflect.Uint64, reflect.Uint32, reflect.Uint, reflect.Uint16, reflect.Uint8:
		vuint64, err := strconv.ParseUint(val, 10, 64)
		if err != nil {
			return err
		}
		fv.SetUint(vuint64)
		return nil
	case reflect.Float32, reflect.Float64:
		vfloat, err := strconv.ParseFloat(val, 64)
		if err != nil {
			return err
		}
		fv.SetFloat(vfloat)
		return nil
	case reflect.Bool:
		vbool, err := strconv.ParseBool(val)
		if err != nil {
			return err
		}
		fv.SetBool(vbool)
		return nil
	case reflect.String:
		fv.SetString(val)
		return nil
	case reflect.Complex64, reflect.Complex128:
		vcm, err := strconv.ParseComplex(val, 128)
		if err != nil {
			return err
		}
		fv.SetComplex(vcm)
		return nil
	case reflect.Struct:
		if reflect.PointerTo(fv.Type()).Implements(textUnmarshaller) {
			err := fv.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(val))
			if err != nil {
				return err
			}
			return nil
		}
	}
	return fmt.Errorf("unknown kind:%v Type: %s", fd.Kind(), fd.Name())
}
